使用 IPVS 实现 Kubernetes 入口流量负载均衡
新搭建的 Kubernetes 集群如何承接外部访问的流量,是刚上手 Kubernetes 时常常会遇到的问题。 在公有云上,官方给出了比较直接的答案,使用 LoadBalancer 类型的 Service,利用公有云提供的负载均衡服务来承接流量,同时在多台服务器之间进行负载均衡。
而在私有环境中,如何正确的将外部流量引入到集群内部,却暂时没有标准的做法。 本文将介绍一种基于 IPVS 来承接流量并实现负载均衡的方法,供大家参考。在阅读本文前建议先了解文中相关基础知识点,推荐阅读下「浅析从外部访问 Kubernetes 集群中应用的几种方式」一文。
IPVS
IPVS 是 LVS 项目的一部分,是一款运行在 Linux kernel 当中的 4 层负载均衡器,性能异常优秀。 根据这篇文章的介绍,使用调优后的内核,可以轻松处理每秒 10 万次以上的转发请求。目前在中大型互联网项目中,IPVS 被广泛的使用,用于承接网站入口处的流量。
Kubernetes Service
Service 是 Kubernetes 的基础概念之一,它将一组 Pod 抽象成为一项服务,统一的对外提供服务,在各个 Pod 之间实现负载均衡。 Service 有多种类型,最基本的 ClusterIP 类型解决了集群内部访问服务的需求,NodePort 类型通过 Node 节点的端口暴露服务, 再配合上 LoadBalancer 类型所定义的负载均衡器,实现了流量经过前端负载均衡器分发到各个 Node 节点暴露出的端口, 再通过 IPtables进行一次负载均衡,最终分发到实际的 Pod 上这个过程。
在 Service 的 Spec 中,externalIPs 字段平常鲜有人提到,当把 IP 地址填入这个字段后,Kube-Proxy 会增加对应的 IPtables 规则,当有以对应 IP 为目标的流量发送到 Node节点时,IPtables将进行 NAT,将流量转发到对应的服务上。一般情况下,很少会遇到服务器接受非自身绑定 IP 流量的情况,所以 externalIPs 不常被使用,但配合网络层的其他工具,它可以实现给 Service 绑定外部 IP 的效果。
今天我们将使用 externalIPs 配合 IPVS 的 DR(Direct Routing )模式实现将外部流量引入到集群内部,同时实现负载均衡。
环境搭建
为了演示,我们搭建了 4 台服务器组成的集群。一台服务器运行 IPVS,扮演负载均衡器的作用。一台服务器运行 Kubernetes Master 组件,其他两台服务器作为 Node 加入到 Kubernetes 集群当中。搭建过程这里不详细介绍,大家可以参考相关文档,比如:「和我一步步部署 kubernetes 集群」。
所有服务器在 172.17.8.0/24 这个网段中,服务的 VIP 我们设定为 172.17.8.201。整体架构如下图所示:
接下来让我们来配置 IPVS 和 Kubernetes。
使用 externalIPs 暴露 Kubernetes Service
首先在集群内部运行 2 个 nginx Pod 用作演示。
$ kubectl run nginx --image=nginx --replicas=2
再将它暴露为 Service,同时设定 externalIPs 字段
$ kubectl expose deployment nginx --port 80 --external-ip 172.17.8.201
查看 IPtables 配置,确认对应的 IPtables 规则已经被加入。
$ sudo iptables -t nat -L KUBE-SERVICES -n
Chain KUBE-SERVICES (2 references)
target prot opt source destination
KUBE-SVC-4N57TFCL4MD7ZTDA tcp -- 0.0.0.0/0 10.3.0.156 /* default/nginx: cluster IP */ tcp dpt:80
KUBE-MARK-MASQ tcp -- 0.0.0.0/0 172.17.8.201 /* default/nginx: external IP */ tcp dpt:80
KUBE-SVC-4N57TFCL4MD7ZTDA tcp -- 0.0.0.0/0 172.17.8.201 /* default/nginx: external IP */ tcp dpt:80 PHYSDEV match ! --physdev-is-in ADDRTYPE match src-type !LOCAL
KUBE-SVC-4N57TFCL4MD7ZTDA tcp -- 0.0.0.0/0 172.17.8.201 /* default/nginx: external IP */ tcp dpt:80 ADDRTYPE match dst-type LOCAL
KUBE-SVC-NPX46M4PTMTKRN6Y tcp -- 0.0.0.0/0 10.3.0.1 /* default/kubernetes:https cluster IP */ tcp dpt:443
KUBE-NODEPORTS all -- 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
配置 IPVS 实现流量转发
首先在 IPVS 服务器上,打开 ipv4_forward。
$ sudo sysctl -w net.ipv4.ip_forward=1
接下来加载 IPVS 内核模块。
$ sudo modprobe ip_vs
将 VIP 绑定在网卡上。
$ sudo ifconfig eth0:0 172.17.8.201 netmask 255.255.255.0 broadcast 172.17.8.255
再使用 ipvsadm 来配置 IPVS,这里我们直接使用 Docker 镜像,避免和特定发行版绑定。
$ docker run --privileged -it --rm --net host luizbafilho/ipvsadm
/ # ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
/ # ipvsadm -A -t 172.17.8.201:80
/ # ipvsadm -a -t 172.17.8.201:80 -r 172.17.8.11:80 -g
/ # ipvsadm -a -t 172.17.8.201:80 -r 172.17.8.12:80 -g
/ # ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
-> RemoteAddress:Port Forward Weight ActiveConn InActConn
TCP 172.17.8.201:http wlc
-> 172.17.8.11:http Route 1 0 0
-> 172.17.8.12:http Route 1 0 0
可以看到,我们成功建立了从 VIP 到后端服务器的转发。
验证转发效果
首先使用 curl 来测试是否能够正常访问 Nginx 服务。
$ curl http://172.17.8.201
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
接下来在 172.17.8.11 上抓包来确认 IPVS 的工作情况。
$ sudo tcpdump -i any port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
04:09:07.503858 IP 172.17.8.1.51921 > 172.17.8.201.http: Flags [S], seq 2747628840, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 1332071005 ecr 0,sackOK,eol], length 0
04:09:07.504241 IP 10.2.0.1.51921 > 10.2.0.3.http: Flags [S], seq 2747628840, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 1332071005 ecr 0,sackOK,eol], length 0
04:09:07.504498 IP 10.2.0.1.51921 > 10.2.0.3.http: Flags [S], seq 2747628840, win 65535, options [mss 1460,nop,wscale 5,nop,nop,TS val 1332071005 ecr 0,sackOK,eol], length 0
04:09:07.504827 IP 10.2.0.3.http > 10.2.0.1.51921: Flags [S.], seq 3762638044, ack 2747628841, win 28960, options [mss 1460,sackOK,TS val 153786592 ecr 1332071005,nop,wscale 7], length 0
04:09:07.504827 IP 10.2.0.3.http > 172.17.8.1.51921: Flags [S.], seq 3762638044, ack 2747628841, win 28960, options [mss 1460,sackOK,TS val 153786592 ecr 1332071005,nop,wscale 7], length 0
04:09:07.504888 IP 172.17.8.201.http > 172.17.8.1.51921: Flags [S.], seq 3762638044, ack 2747628841, win 28960, options [mss 1460,sackOK,TS val 153786592 ecr 1332071005,nop,wscale 7], length 0
04:09:07.505599 IP 172.17.8.1.51921 > 172.17.8.201.http: Flags [.], ack 1, win 4117, options [nop,nop,TS val 1332071007 ecr 153786592], length 0
可以看到,由客户端 172.17.8.1 发送给 172.17.8.201 的封包,经过 IPVS 的中转发送给了 172.17.8.11 这台服务器,并经过 NAT 后发送给了 10.2.0.3 这个 Pod。返回的封包不经过 IPVS 服务器直接从 172.17.8.11 发送给了 172.17.8.1。 说明 IPVS 的 DR 模式工作正常。重复多次测试可以看到流量分别从 172.17.8.11 和 172.17.8.12 进入,再分发给不同的 Pod,说明负载均衡工作正常。
与传统的 IPVS DR 模式配置不同的是,我们并未在承接流量的服务器上执行绑定 VIP,再关闭 ARP 的操作。 那是因为对 VIP 的处理直接发生在 IPtables上,我们无需在服务器上运行程序来承接流量,IPtables 会将流量转发到对应的 Pod 上。
使用这种方法来承接流量,仅需要配置 externalIPs 为 VIP 即可,无需对服务器做任何特殊的设置,使用起来相当方便。
总结
在本文中演示了使用 IPVS 配合 externalIPs 实现将外部流量导入到 Kubernetes 集群中,并实现负载均衡的方法。 希望可以帮助大家理解 IPVS 和 externalIPs 的工作原理,以便在恰当的场景下合理使用这两项技术解决问题。 实际部署时,还需要考虑后台服务器可用性检查,IPVS 节点主从备份,水平扩展等问题。在这里就不详细介绍了。
在 Kubernetes 中还有许多与 externalIPs 类似的非常用功能,有些甚至是使用 Annotation 来进行配置,将来有机会再进一步分享。
来源:极术
原文:http://t.cn/RX8vzvC
题图:来自谷歌图片搜索
版权:本文版权归原作者所有
今日思想
太想要一样东西,就是失去的开始,得不到,会朝思暮想,得到了,又害怕失去,一把沙抓的越紧,流失的越多,一个人,抱的越用力,他走的越快,当你张开双臂,两手空空,你怀里拥抱的是整个世界,在你什么都不想要的时候,得到的一切都是意外之喜。
——一禅小和尚
推荐阅读